//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "Base.h"

Define_Module(Base);

void Base::initialize()
{
    numOfFrames = getAncestorPar("num_of_frames");
    ARQ_Block_Size = getAncestorPar("arq_block_size");//(bytes)
    PDU_Size = getAncestorPar("pdu_size");//(blocks)
    frameSize = getAncestorPar("data_size");
    numOfBlocks = frameSize/ARQ_Block_Size;

    curFrameCC = 1;
    curFrameCS = 1;
    curFramePIC = 1;
    curFramePIS = 1;
    curFramePII = 1;
    curFrameP3C = 1;
    curFrameP3S = 1;

    fillStack(Conventional_Selective);
    fillStack(Proposal_I_Selective);
    fillStack(Proposal_II);
    fillStack(Proposal_III_Cumulative);
    fillStack(Proposal_III_Selective);

    Data *firstmsg = new Data("Starting");
    firstmsg->setBSN(0);

    //    Initialize communication with ConventionalCumulative module
    printMessage(0, curFrameCC,"Conventional_Cumulative");
    send(firstmsg->dup(),"toConC");

    //    Initialize communication with ConventionalSelective module
    initConvSel();

    //    Initialize communication with ProposalICumulative module
    printMessage(0, curFramePIC,"Proposal_I_Cumulative");
    send(firstmsg->dup(),"toPr1C");

    //    Initialize communication with ProposalISelective module
    initPrISel();

    initPrII();

    initPr3C();

    initPr3S();
}

void Base::handleMessage(cMessage *msg)
{
    if(strcmp(msg->getArrivalGate()->getName(), "fromConC")==0)
        handleConvC(check_and_cast<ARQ_pck *>(msg));
    else if(strcmp(msg->getArrivalGate()->getName(), "fromConS")==0)
        handleConvSel(check_and_cast<ARQ_pck *>(msg));
    else if(strcmp(msg->getArrivalGate()->getName(), "fromPr1C")==0)
        handlePrIC(check_and_cast<ARQ_pck *>(msg));
    else if(strcmp(msg->getArrivalGate()->getName(), "fromPr1S")==0)
        handlePrISel(check_and_cast<ARQ_pck *>(msg));
    else if(strcmp(msg->getArrivalGate()->getName(), "fromPr2")==0)
        handlePrII(check_and_cast<ARQ_pck *>(msg));
    else if(strcmp(msg->getArrivalGate()->getName(), "fromPr3C")==0)
        handlePr3C(check_and_cast<ARQ_pck *>(msg));
    else if(strcmp(msg->getArrivalGate()->getName(), "fromPr3S")==0)
        handlePr3S(check_and_cast<ARQ_pck *>(msg));
}

void Base::handleConvC(ARQ_pck *msg)
{
    int ackBSN = msg->getBSN();
    bool lastBlockReceived = msg->getLast();
    delete(msg);
    Data *datamsg = new Data("data");
    if( lastBlockReceived )                 //finished a frame
    {
        datamsg->setBSN(0);
        curFrameCC++;
        if(curFrameCC < numOfFrames)
            printMessage(0, curFrameCC, "Conventional_Cumulative");
    }
    else
    {
        datamsg->setBSN(ackBSN+1);
        printMessage(ackBSN+1, curFrameCC, "Conventional_Cumulative");
    }
    if(curFrameCC <= numOfFrames)
        send(datamsg,"toConC");
    else                                //finished a set of frames, up the BLER by 1%
    {
        curFrameCC = 1;
        datamsg->setBLERup(true);
        upARQSize();
        printMessage(0, curFrameCC, "Conventional_Cumulative");
        send(datamsg,"toConC");
    }
}

void Base::initConvSel()
{
    for(int i=0 ; !stackConventional.empty(); i+=PDU_Size)
    {
        Data *msg = new Data("Data");
        int stackLen = (int)stackConventional.size();
        int size = PDU_Size>stackLen ? stackLen : PDU_Size;
        msg->setBlocksArraySize(size);
        sendConvSel(msg);
    }
}

void Base::sendConvSel(Data *msg)
{
    int stop = msg->getBlocksArraySize();;
    for(int i=0 ; i<stop && !stackConventional.empty() ; i++)
    {
        int temp = stackConventional.top();
        stackConventional.pop();
        msg->setBlocks(i, temp);
    }
    if(stackConventional.empty())
        msg->setLast(true);
    printMessage(msg, curFrameCS, "ConventionalSelective");
    send(msg,"toConS");
}

void Base::handleConvSel(ARQ_pck *msg)
{
    int index;
    int blockNum;

    if( msg->getLast() )
    {
        curFrameCS++;
        if(curFrameCS <= numOfFrames)
        {
            EV<<"Finished sending frame #"<<curFrameCS-1<<" to ConventionalSelective"<<endl;
            fillStack(Conventional_Selective);
            initConvSel();
        }
        else                        //finished a set of frames, up the BLER by 1%
        {
            curFrameCS = 0;
            Data *datamsg = new Data("data");
            upARQSize();
            datamsg->setBLERup(true);
            send(datamsg,"toConS");
        }
    }
    else
    {
        for(index=msg->getRcvdBlocksArraySize()-1 ; index>=0; index--)
        {
            blockNum =  msg->getRcvdBlocks(index);
            if(blockNum >= 0)
                stackConventional.push(blockNum);
        }
        initConvSel();
    }
    delete(msg);
}

void Base::handlePrIC(ARQ_pck *msg)
{
    int ackBSN = msg->getBSN();
    bool lastBlockReceived = msg->getLast();
    delete(msg);
    Data *datamsg = new Data("data");
    if( lastBlockReceived )                 //finished a frame
    {
        datamsg->setBSN(0);
        curFramePIC++;
        if(curFramePIC < numOfFrames)
            printMessage(0, curFramePIC, "Proposal_I_Cumulative");
    }
    else
    {
        datamsg->setBSN(ackBSN+1);
        printMessage(ackBSN+1, curFramePIC, "Proposal_I_Cumulative");
    }
    if(curFramePIC <= numOfFrames)
        send(datamsg,"toPr1C");
    else                                //finished a set of frames, up the BLER by 1%
    {
        curFramePIC= 1;
        upARQSize();
        datamsg->setBLERup(true);
        printMessage(0, curFramePIC, "Proposal_I_Cumulative");
        send(datamsg,"toPr1C");
    }
}

void Base::initPrISel()
{
    for(int i=0 ; !stackProposalI.empty(); i+=PDU_Size)
    {
        Data *msg = new Data("Data");
        int stackLen = (int)stackProposalI.size();
        int size = PDU_Size>stackLen ? stackLen : PDU_Size;
        msg->setBlocksArraySize(size);
        sendPrISel(msg);
    }
}

void Base::sendPrISel(Data *msg)
{
    int stop = msg->getBlocksArraySize();;
    for(int i=0 ; i<stop && !stackProposalI.empty() ; i++)
    {
        int temp = stackProposalI.top();
        stackProposalI.pop();
        msg->setBlocks(i, temp);
    }
    if(stackProposalI.empty())
        msg->setLast(true);
    printMessage(msg, curFramePIS, "Proposal_I_Selective");
    send(msg,"toPr1S");
}

void Base::handlePrISel(ARQ_pck *msg)
{
    int index;
    int blockNum;

    if( msg->getLast() )
    {
        curFramePIS++;
        if(curFramePIS <= numOfFrames)
        {
            EV<<"Finished sending frame #"<<curFramePIS-1<<" to Proposal_I_Selective"<<endl;
            fillStack(Proposal_I_Selective);
            initPrISel();
        }
        else                        //finished a set of frames, up the BLER by 1%
        {
            curFramePIS = 0;
            Data *datamsg = new Data("data");
            upARQSize();
            datamsg->setBLERup(true);
            send(datamsg,"toPr1S");
        }
    }
    else
    {
        for(index=msg->getRcvdBlocksArraySize()-1 ; index>=0; index--)
        {
            blockNum =  msg->getRcvdBlocks(index);
            if(blockNum >= 0)
                stackProposalI.push(blockNum);
        }
        initPrISel();
    }
    delete(msg);
}

void Base::initPrII()
{
    for(int i=0 ; !stackProposalII.empty(); i+=PDU_Size)
    {
        Data *msg = new Data("Data");
        int stackLen = (int)stackProposalII.size();
        int size = PDU_Size>stackLen ? stackLen : PDU_Size;
        msg->setBlocksArraySize(size);
        sendPrII(msg);
    }
}

void Base::sendPrII(Data *msg)
{
    int stop = msg->getBlocksArraySize();;
    for(int i=0 ; i<stop && !stackProposalII.empty() ; i++)
    {
        int temp = stackProposalII.top();
        stackProposalII.pop();
        msg->setBlocks(i, temp);
    }
    if(stackProposalII.empty())
        msg->setLast(true);
    printMessage(msg, curFramePII, "Proposal_II");
    send(msg,"toPr2");
}

void Base::handlePrII(ARQ_pck *msg)
{
    int index;
    int blockNum;

    if( msg->getLast() )
    {
        curFramePII++;
        if(curFramePII <= numOfFrames)
        {
            EV<<"Finished sending frame #"<<curFramePII-1<<" to Proposal_II"<<endl;
            fillStack(Proposal_II);
            initPrII();
        }
        else                        //finished a set of frames, up the BLER by 1%
        {
            curFramePII = 0;
            Data *datamsg = new Data("data");
            upARQSize();
            datamsg->setBLERup(true);
            send(datamsg,"toPr2");
        }
    }
    else
    {
        for(index=msg->getRcvdBlocksArraySize()-1 ; index>=0; index--)
        {
            blockNum =  msg->getRcvdBlocks(index);
            if(blockNum >= 0)
                stackProposalII.push(blockNum);
        }
        initPrII();
    }
    delete(msg);
}

void Base::initPr3C()
{
    for(int i=0 ; !stackProposal3C.empty(); i+=PDU_Size)
    {
        Data *msg = new Data("Data");
        msg->setType(Proposal_II);
        int stackLen = (int)stackProposal3C.size();
        int size = PDU_Size>stackLen ? stackLen : PDU_Size;
        msg->setBlocksArraySize(size);
        sendPr3C(msg);
    }
}

void Base::sendPr3C(Data *msg)
{
    int stop = msg->getBlocksArraySize();;
    for(int i=0 ; i<stop && !stackProposal3C.empty() ; i++)
    {
        int temp = stackProposal3C.top();
        stackProposal3C.pop();
        msg->setBlocks(i, temp);
    }
    if(stackProposal3C.empty())
        msg->setLast(true);
    printMessage(msg, curFrameP3C, "Proposal_III_Cumulative");
    send(msg,"toPr3C");
}

void Base::handlePr3C(ARQ_pck *msg)
{
    int index;
    int blockNum;

    if( msg->getLast() )
    {
        curFrameP3C++;
        if(curFrameP3C <= numOfFrames)
        {
            EV<<"Finished sending frame #"<<curFrameP3C-1<<" to Proposal_III_Cumulative"<<endl;
            fillStack(Proposal_III_Cumulative);
            initPr3C();
        }
        else                        //finished a set of frames, up the BLER by 1%
        {
            curFrameP3C = 0;
            Data *datamsg = new Data("data");
            upARQSize();
            datamsg->setBLERup(true);
            send(datamsg,"toPr3C");
        }
    }
    else if(msg->getType()==Proposal_II)
    {
        for(index=msg->getRcvdBlocksArraySize()-1 ; index>=0; index--)
        {
            blockNum =  msg->getRcvdBlocks(index);
            if(blockNum >= 0)
                stackProposal3C.push(blockNum);
        }
        initPr3C();
    }
    else
    {
        Data *datamsg = new Data("data");
        datamsg->setType(Proposal_I_Cumulative);
        datamsg->setBSN(msg->getBSN()+1);
        datamsg->setLast(true);
        send(datamsg,"toPr3C");
    }
    delete(msg);
}

void Base::initPr3S()
{
    for(int i=0 ; !stackProposal3S.empty(); i+=PDU_Size)
    {
        Data *msg = new Data("Data");
//        msg->setType(Proposal_II);
        int stackLen = (int)stackProposal3S.size();
        int size = PDU_Size>stackLen ? stackLen : PDU_Size;
        msg->setBlocksArraySize(size);
        sendPr3S(msg);
    }
}

void Base::sendPr3S(Data *msg)
{
    int stop = msg->getBlocksArraySize();;
    for(int i=0 ; i<stop && !stackProposal3S.empty() ; i++)
    {
        int temp = stackProposal3S.top();
        stackProposal3S.pop();
        msg->setBlocks(i, temp);
    }
    if(stackProposal3S.empty())
        msg->setLast(true);
    printMessage(msg, curFrameP3S, "Proposal_III_Selective");
    send(msg,"toPr3S");
}

void Base::handlePr3S(ARQ_pck *msg)
{
    int index;
    int blockNum;

    if( msg->getLast() )
    {
        curFrameP3S++;
        if(curFrameP3S <= numOfFrames)
        {
            EV<<"Finished sending frame #"<<curFrameP3S-1<<" to Proposal_III_Selective"<<endl;
            fillStack(Proposal_III_Selective);
            initPr3S();
        }
        else                        //finished a set of frames, up the BLER by 1%
        {
            curFrameP3S = 0;
            Data *datamsg = new Data("data");
            upARQSize();
            datamsg->setBLERup(true);
            send(datamsg,"toPr3S");
        }
    }
    else
    {
        for(index=msg->getRcvdBlocksArraySize()-1 ; index>=0; index--)
        {
            blockNum =  msg->getRcvdBlocks(index);
            if(blockNum >= 0)
                stackProposal3S.push(blockNum);
        }
        initPr3S();
    }
    delete(msg);
}


void Base::printMessage(Data *msg, int frame, string dest)
{
    int stop = msg->getBlocksArraySize();
    EV<<"Sending blocks : { ";
    for(int i=0 ; i<stop ; i++)
    {
        int temp = msg->getBlocks(i);
        if(temp >= 0 )
            EV<<temp<<" ";
        else if(temp == -3 )
            EV<<" _ ";
    }
    EV<<"}"<<" from frame #"<<frame<<" to "<<dest<<endl;
}

void Base::fillStack(int type)
{
    numOfBlocks = frameSize/ARQ_Block_Size;
    switch(type)
    {
        case Conventional_Selective:
            for(int i=numOfBlocks-1 ; i>=0 ; i--)
                stackConventional.push(i);
            break;
        case Proposal_I_Selective:
            for(int i=numOfBlocks-1 ; i>=0 ; i--)
                stackProposalI.push(i);
            break;
        case Proposal_II:
            for(int i=numOfBlocks-1 ; i>=0 ; i--)
                stackProposalII.push(i);
            break;
        case Proposal_III_Cumulative:
            for(int i=numOfBlocks-1 ; i>=0 ; i--)
                stackProposal3C.push(i);
            break;
        case Proposal_III_Selective:
            for(int i=numOfBlocks-1 ; i>=0 ; i--)
                stackProposal3S.push(i);
            break;
        default:
            break;
    }

}

void Base::upARQSize()
{
    int type = getAncestorPar("simtype");
    if(type==2)
    {
        if(ARQ_Block_Size==16)
            ARQ_Block_Size=64;
        else if(ARQ_Block_Size==64)
            ARQ_Block_Size=128;
        else if(ARQ_Block_Size==128)
            ARQ_Block_Size=256;
        else if(ARQ_Block_Size==256)
            ARQ_Block_Size=512;
        else if(ARQ_Block_Size==512)
            ARQ_Block_Size=1024;
    }
}

void Base::printMessage(int block, int frame, string dest)
{
    EV<<"Sending blocks : { ";
    for(int index = block ; index<block+PDU_Size && index<numOfBlocks ; index++)
        EV<<index<<" ";
    EV<<"} from frame #"<<frame<<" to "<<dest<<endl;
}














